package com.simple4java.biglist;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

public class BigList<E> implements List<E> {

	/**
	 * 
	 * Node data structure
	 * 
	 */
	private class Node {
		public long nodeStart = -1;
		public long nodeEnd = -1;
		public int hashcode;
	}

	/**
	 * Index List for the full list
	 */
	private Vector<Node> indexList = new Vector<Node>();

	/**
	 * List for deleted nodes
	 */
	private Vector<Node> delList = new Vector<Node>();

	/**
	 * segment id
	 */
	private long segment = 0;

	/**
	 * current file index position
	 */
	private long currentPos = 0;

	/**
	 * mapped buffer of the segment
	 */
	MappedByteBuffer out = null;

	/**
	 * The random access file
	 */
	RandomAccessFile memoryMappedFile = null;

	/**
	 * Max size of the segment loaded in memory
	 */
	private long maxLimit = 0;

	/**
	 * logger
	 */
	private Logger logger = Logger.getLogger("MemList");

		

	public BigList(long maxMem, String file) throws IOException {
		maxLimit = maxMem;
		File fle = new File(file);
		if (fle.exists()) {
			fle.delete();
		}
		memoryMappedFile = new RandomAccessFile(file, "rw");

		// Mapping a file into memory
		out = memoryMappedFile.getChannel().map(FileChannel.MapMode.READ_WRITE,
				0, maxMem);
	}

	@Override
	public synchronized boolean add(E arg0) {
		// TODO Auto-generated method stubect
		boolean posChanged = false;
		boolean added = false;
		try {
			ByteArrayOutputStream bos = new ByteArrayOutputStream();

			ObjectOutputStream oos = new ObjectOutputStream(bos);
			oos.writeObject(arg0);
			byte[] yourBytes = bos.toByteArray();
			oos.flush();
			oos.close();
			bos.close();
			long currPos = currentPos;
			for (Node nd : delList) {
				if (nd.nodeEnd - nd.nodeStart >= yourBytes.length) {
					currentPos = nd.nodeStart;
					posChanged = true;
					delList.remove(nd);
					break;
				}
			}

			int max = yourBytes.length;
			int cnt = 0;
			long seg = currentPos / maxLimit;
			moveSegment(seg);

			MappedByteBuffer mout = (MappedByteBuffer) out.duplicate();
			long start = 0;
			if (segment == 0) {
				start = currentPos;
			} else {
				start = currentPos % (segment * maxLimit);
			}
			mout.position((int) start);

			Node nd2 = new Node();
			nd2.hashcode = arg0.hashCode();
			if (indexList.size() > 0) {
				nd2.nodeStart = currentPos;
				indexList.add(nd2);
			} else {

				nd2.nodeStart = currentPos;
				indexList.add(nd2);
			}

			for (int i = 0; i < max; i++) {
				if (i >= (mout.capacity() - start)) {
					mout = memoryMappedFile.getChannel()
							.map(FileChannel.MapMode.READ_WRITE, currentPos,
									max - i);
					max = max - i;
					i = 0;
					start = 0;

				}
				mout.put(yourBytes[cnt++]);
				currentPos++;
			}
			nd2.nodeEnd = currentPos;
			if (posChanged) {
				currentPos = currPos;
			}
			added = true;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			logger.log(Level.SEVERE, e.getMessage());
			added = false;
		}
		return added;
	}

	private synchronized void moveSegment(long seg) {
		if (segment != seg) {
			try {
				out.clear();
				out = memoryMappedFile.getChannel().map(
						FileChannel.MapMode.READ_WRITE, seg * maxLimit,
						maxLimit);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				logger.log(Level.SEVERE, e.getMessage());
			}
			segment = seg;
		}
	}

	@Override
	public void add(int ind, E arg0) {

		add(arg0);
		Node nd = indexList.remove(indexList.size() - 1);
		indexList.add(ind, nd);

	}

	@Override
	public boolean addAll(Collection<? extends E> arg0) {
		// TODO Auto-generated method stub
		boolean added = false;
		for (E e : arg0) {
			added = add(e);
			if (!added)
				break;
		}
		return added;
	}

	@Override
	public boolean addAll(int arg0, Collection<? extends E> arg1) {
		// TODO Auto-generated method stub
		int ind = arg0;
		boolean added;
		for (E e : arg1) {
			add(ind++, e);
		}
		return true;
	}

	@Override
	public void clear() {
		// TODO Auto-generated method stub

		indexList.clear();
		delList.clear();

	}

	@Override
	public boolean contains(Object arg0) {
		// TODO Auto-generated method stub
		for (Node nd : indexList) {
			if (nd.hashcode == arg0.hashCode() && !delList.contains(nd)) {
				return true;
			}
		}
		return false;
	}

	@Override
	public boolean containsAll(Collection<?> arg0) {
		// TODO Auto-generated method stub
		for (Object e : arg0) {
			boolean found = false;
			for (Node nd : indexList) {
				if (nd.hashcode == e.hashCode()) {
					found = true;
					break;
				}
			}
			if (found == false) {
				return false;
			}
		}
		return true;
	}

	@Override
	public synchronized E get(int ind) {
		// TODO Auto-generated method stub

		Node nd = indexList.get(ind);

		long pos = nd.nodeStart;
		long seg = pos / maxLimit;
		moveSegment(seg);

		long max = nd.nodeEnd - nd.nodeStart;
		try {
			ByteBuffer mout = out.duplicate();

			byte[] res = new byte[(int) max];
			int cnt = 0;
			long start = 0;
			if (segment == 0) {
				start = pos;
			} else {
				start = pos % (segment * maxLimit);
			}
			long limit = max + start;
			for (int i = (int) start; i < limit; i++) {

				if (i >= mout.capacity()) {
					max = max - (i - start);
					limit = max;

					mout = memoryMappedFile.getChannel().map(
							FileChannel.MapMode.READ_ONLY,
							i + (segment * maxLimit), max);
					i = 0;
				}
				res[cnt++] = mout.get(i);
			}

			ByteArrayInputStream bis = new ByteArrayInputStream(res);
			ObjectInputStream ois = new ObjectInputStream(bis);
			E ret = (E) ois.readObject();
			ois.close();
			bis.close();
			return ret;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			logger.log(Level.SEVERE, e.getMessage());
		}
		return null;
	}

	@Override
	public int indexOf(Object arg0) {
		int i = -1;
		for (Node nd : indexList) {
			i++;
			if (nd.hashcode == arg0.hashCode()) {
				return i;
			}
		}

		return i;
	}

	@Override
	public boolean isEmpty() {
		// TODO Auto-generated method stub
		return indexList.isEmpty();
	}

	@Override
	public Iterator<E> iterator() {
		// TODO Auto-generated method stub
		return new Iterator<E>() {
			int index = 0;

			@Override
			public boolean hasNext() {
				// TODO Auto-generated method stub
				return BigList.this.indexList.size() > index;
			}

			@Override
			public E next() {
				// TODO Auto-generated method stub
				return BigList.this.get(index++);
			}

			@Override
			public void remove() {
				// TODO Auto-generated method stub
				BigList.this.remove(index--);
			}
		};
	}

	@Override
	public int lastIndexOf(Object arg0) {
		int i = -1;
		ArrayList<Node> newList = new ArrayList<Node>(indexList);
		Collections.reverse(newList);
		for (Node nd : newList) {
			i++;
			if (nd.hashcode == arg0.hashCode()) {
				return i;
			}
		}

		return i;
	}

	@Override
	public ListIterator<E> listIterator() {
		return listIterator(0);
	}

	@Override
	public ListIterator<E> listIterator(final int ind) {
		// TODO Auto-generated method stub
		return new ListIterator<E>() {
			int index = ind;

			@Override
			public void add(E e) {
				// TODO Auto-generated method stub
				BigList.this.add(index++, e);
			}

			@Override
			public boolean hasNext() {
				// TODO Auto-generated method stub
				return BigList.this.indexList.size() > index;
			}

			@Override
			public boolean hasPrevious() {
				// TODO Auto-generated method stub
				return index > 0;
			}

			@Override
			public E next() {
				// TODO Auto-generated method stub
				return BigList.this.get(index++);
			}

			@Override
			public int nextIndex() {
				// TODO Auto-generated method stub
				return index + 1 < indexList.size() ? index + 1 : indexList
						.size();
			}

			@Override
			public E previous() {
				// TODO Auto-generated method stub
				if (hasPrevious()) {
					return BigList.this.get(--index);
				} else {
					return null;
				}
			}

			@Override
			public int previousIndex() {
				// TODO Auto-generated method stub
				return index - 1 < 0 ? -1 : index - 1;
			}

			@Override
			public void remove() {
				// TODO Auto-generated method stub
				BigList.this.remove(index--);
			}

			@Override
			public void set(E e) {
				// TODO Auto-generated method stub
				BigList.this.set(index++, e);
			}
		};
	}

	@Override
	public boolean remove(Object arg0) {
		// TODO Auto-generated method stub
		int i = -1;
		boolean found = false;
		Node delNode = null;
		for (Node nd : indexList) {
			i++;
			if (nd.hashcode == arg0.hashCode()) {
				delList.add(nd);
				delNode = nd;
				found = true;
				break;
			}
		}
		if (null != delNode) {
			indexList.remove(delNode);
		}
		return found;
	}

	@Override
	public E remove(int arg0) {
		E node = get(arg0);
		remove(node);
		return node;
	}

	@Override
	public boolean removeAll(Collection<?> arg0) {
		boolean rem = false;
		for (Object e : arg0) {
			rem = remove(e);
			if (!rem)
				break;
		}

		return rem;
	}

	private boolean isInCollection(Node nd, Collection<?> arg0) {
		for (Object obj : arg0) {
			if (obj.hashCode() == nd.hashcode) {
				return true;
			}

		}
		return false;
	}

	@Override
	public boolean retainAll(Collection<?> arg0) {
		int i = -1;
		boolean add = false;
		for (Node nd : indexList) {
			i++;

			if (!isInCollection(nd, arg0)) {
				delList.add(nd);
				add = indexList.remove(nd);
				if (!add)
					break;
			}
		}
		return add;
	}

	@Override
	public E set(int arg0, E arg1) {
		// TODO Auto-generated method stubode;
		E node = get(arg0);
		add(arg0, arg1);
		if (arg0 + 1 < indexList.size())
			remove(arg0 + 1);
		return node;
	}

	@Override
	public int size() {

		return indexList.size();
	}

	@Override
	public List<E> subList(int arg0, int arg1) {
		// TODO Auto-generated method stub
		List<E> lst = new ArrayList<E>();
		for (int i = arg0; i <= arg1; i++) {
			lst.add(get(i));
		}
		return lst;
	}

	@Override
	public Object[] toArray() {
		List<E> lst = new ArrayList<E>();
		for (int i = 0; i < indexList.size(); i++) {
			lst.add(get(i));
		}
		return lst.toArray(new Object[0]);
	}

	@Override
	public <T> T[] toArray(T[] arg0) {
		// TODO Auto-generated method stub
		Object[] arr = toArray();
		if (arg0.length < arr.length) {
			List<E> lst = new ArrayList<E>();
			for (int i = 0; i < indexList.size(); i++) {
				lst.add(get(i));
			}
			return lst.toArray(arg0);
		}
		int ind = 0;
		for (Object obj : arr) {
			T t = (T) obj;
			arg0[ind++] = t;
		}
		return arg0;
	}

}
